package nosql.util;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.List;

import nosql.property.RecommendedCondition;
import Jama.Matrix;

/**
 * Fuzzy c-means (FCM) is a method of clustering which allows one 
 * piece of data to belong to two or more clusters. This method 
 * (developed by Dunn in 1973 and improved by Bezdek in 1981) is 
 * frequently used in pattern recognition. 
 * @author Yexi Jiang <http://sites.google.com/site/yexijiang/>
 *
 */
public class FuzzyCMeans {
	private int m;
	private int clusterNumber;
	private double epsilon;
	private int elementSize;
	private List<String> elementNamesList;
	private Matrix membershipMatrix;	//	the memebership matrix
	private List<RecommendedCondition>[] elements;	//	the adjcency list of each element
	private double[][] clusterCenters;	//	row represent each cluster
	
	public FuzzyCMeans(int k, List<RecommendedCondition>[] elements, List<String> elementNamesList, double epsilon){
		clusterNumber = k;
		elementSize = elements.length;
		this.elements = elements;
		this.elementNamesList = elementNamesList;
		this.epsilon = epsilon;
		//	init membership matrix as random
		membershipMatrix = Matrix.random(k, elementSize);
		clusterCenters = new Matrix(k, elementSize, 0).getArray();
		m = 2;
	}
	
	public void setM(int m){
		if(m > 0)
			this.m = m;
	}
	
	public double[][] doCluster(){
		
		Matrix copyOldMembershipMatrix = null;
		do{
			copyOldMembershipMatrix = membershipMatrix.copy();
			for(int cluster = 0; cluster < clusterNumber; ++cluster){
				
				//	update center of cluster k
				double[] clusterCenter = new double[elements.length];
				for(int dimension = 0; dimension < elements.length; ++dimension){
					double rowSum = 0.0;
					double[] numerator = new double[elements.length];
					double uij = membershipMatrix.get(cluster, dimension);
					for(int n = 0; n < elements.length; ++n){
						String name = elementNamesList.get(n);
						int idx = elements[dimension].indexOf(name);
						int strength = 0;
						if(idx >= 0)
							strength = elements[dimension].get(idx).getSecond();
						numerator[n] = Math.pow(uij, m) * strength;
						rowSum += Math.pow(uij, 2.0);
					}
					clusterCenter[dimension] = numerator[dimension] / rowSum;
				}
				clusterCenters[cluster] = clusterCenter;
			}
			
			//	update membership matrix
			for(int i = 0; i < elements.length; ++i){
				for(int j = 0; j < clusterNumber; ++j){
					double uij = 1;
					double denominator = 0;
					for(int k = 0; k < clusterNumber; ++k){
						denominator += Math.pow(normRow(i, j, k), 2.0);
					}
					uij /= denominator;
					membershipMatrix.set(j, i, uij);
				}
			}
			
		}while(converge(copyOldMembershipMatrix) == false);
		
		return membershipMatrix.getArray();
	}
	
	private boolean converge(Matrix oldMatrix){
		if(oldMatrix.minus(membershipMatrix).norm2() <= epsilon){
			System.out.println(oldMatrix.minus(membershipMatrix).norm2() + " converge.");
			return true;
		}
		else{
			System.out.println("Not converge.");
			return false;
		}
	}
	
	private double normRow(int xIdx, int cIdxj, int cIdxk){
		double norm = 0.0;
		double[][] rowX = new Matrix(1, elements.length).getArray();
		List<RecommendedCondition> elementStrengtgAdjcencyList = elements[xIdx];
		for(int i = 0; i < elementNamesList.size(); ++i){
			int strengthIdx = elementStrengtgAdjcencyList.indexOf(new RecommendedCondition(elementNamesList.get(i), 0));
			if(strengthIdx < 0){
				rowX[0][i] = 0;
			}else{
				rowX[0][i] = elementStrengtgAdjcencyList.get(strengthIdx).getSecond();
			}
		}
		Matrix mx = new Matrix(rowX);
		double[][] clusterRowj = new double[1][];
		clusterRowj[0] = clusterCenters[cIdxj];
		double[][] clusterRowk = new double[1][];
		clusterRowk[0] = clusterCenters[cIdxk];
		Matrix mcj = new Matrix(clusterRowj);
		Matrix mck = new Matrix(clusterRowk);
//		System.out.print(mx.minus(mcj).norm2() + ":");
//		System.out.println(mx.minus(mck).norm2());
		norm = mx.minus(mcj).norm2() / mx.minus(mck).norm2();
//		System.out.println("norm:" + norm);
		return norm;
	}
	
	public static void main(String[] args) throws Exception{
		Connection connection = null;
		try {
			Class.forName("org.gjt.mm.mysql.Driver");
			connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/nosql", "dev", "dev");
		} catch(ClassNotFoundException e){
			e.printStackTrace();
		} catch (SQLException e) {
			e.printStackTrace();
		}
		
		Statement stmt = connection.createStatement();
		ResultSet rs = stmt.executeQuery("select * from _");
		List<String> elementNames = new ArrayList<String>();
		
		rs.last();
		int size = rs.getRow();
		rs.beforeFirst();
		List<RecommendedCondition>[] elements = new List[size];
		int idx = 0;
		while(rs.next()){
			String name = rs.getString("property");
			String relatedProperties = rs.getString("relatedProperty");
			String[] relatesStrings = relatedProperties.split(",");
			List<RecommendedCondition> elementsList = new ArrayList<RecommendedCondition>();
			for(String relate : relatesStrings){
				String[] record = relate.split(":");
				elementsList.add(new RecommendedCondition(record[0], Integer.parseInt(record[1])));
			}
			elements[idx++] = elementsList;
			elementNames.add(name);
		}
		System.out.println("Begin.");
		
		int k = 3;
		double epsilon = 0.5;
		FuzzyCMeans cmeans = new FuzzyCMeans(k, elements, elementNames, epsilon);
		cmeans.doCluster();
	}
	
}
